// pointsprites.c + my own modification
/*
 * This is a modified point sprites example from the book OpenGLSuperBible. The 
 * original program was written by Richard S. Wright Jr.
 *  
 * I took the texture generator and some ideas for the camera movement from the 
 * nbody example of the CUDA SDK (renderparticles.cpp). 
 * 
 * I am not aware of the licenses involved in these codes, but I think you may 
 * use it as it is without expecting any support. Also, you cannot sell this
 * binary or the source code.
 * 
 * Hope you enjoy.
 * 
 * Pedro Fluxá R.
 *
 */

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <common.h>
#include <parse_config_file.h>

// Global pointer to coordinates
float *vSmallStars;

long SMALL_STARS = 0;

#define true 1
#define false 0

// Default screen size
#define SCREEN_X    640
#define SCREEN_Y    480 


int Width = 800;
int Height = 600;

GLuint texture_name;

// Texture objects
GLuint  textureObjects[1];

// Input data file
FILE *data;

long snapshot = 0;

struct camera
{
	
	float   spin[3];
	float  trans[3];
	float lastxy[2];
	
  float scale;
  
	float inertia  ;
};

// Camera structure
struct camera CAM;

// Create an array of boolean values of length 256 (0-255)
int keyStates[256];   

void loadSnapshot( long  I, FILE *D )
{
	if( data == NULL )
	{
		printf( "CANNOT OPEN FILE! EXITING\n" );
		exit(-4);
	}
  
  if( feof(data) )
		fseek( data, 0, SEEK_SET );
	
	float x,y,z,fooo; 

  double foo;
  float coords[3];
  
  // Populate star list
  for(int i = 0; i < SMALL_STARS && !feof(data); i++)
  {
    fscanf( data, "%lf%f%f%f%f%f%f\n", &foo, &x, &y, &z,&fooo,&fooo,&fooo);
    coords[0] = 1*x;
    coords[1] = 1*y;
    coords[2] = 1*z;
    //printf( "%f %f %f\n", coords[0], coords[1], coords[2] );
    for( int k=0; k<3; k++ )
    {
      vSmallStars[k + 3*i] = coords[k];
    } 
    //printf( "%f %f %f\n", vSmallStars[0+3*i],vSmallStars[1+3*i],vSmallStars[2+3*i]);
  }
  
  printf( "SNAPSHOT %ld WAS LOADED\n", I );
}

void keyPressed (unsigned char key, int x, int y) 
{  
	keyStates[key] = true; // Set the state of the current key to pressed  
}  
  
void keyUp (unsigned char key, int x, int y) 
{  
	keyStates[key] = false; // Set the state of the current key to not pressed  
}  

void keyOperations (void) 
{  
	
	if (keyStates['n']) 
	{
		snapshot++;
		loadSnapshot( snapshot, data );
	}
	
	if (keyStates['b']) 
	{
		snapshot--;
		if( snapshot < 0 )
			snapshot = 0;
	
		loadSnapshot( snapshot, data );
	}   
	
	if (keyStates['w']) 
	{
		CAM.trans[2] += 0.0008;
	}   
	
	if (keyStates['s']) 
	{
		CAM.trans[2] -= 0.0008;
	} 
  
  if (keyStates['c']) 
	{
		CAM.scale *= 2;
	}   
    
}

void cameraMovementFunction(int x, int y) 
{

	GLuint specialKey = glutGetModifiers();

	if (specialKey == GLUT_ACTIVE_SHIFT) {

		CAM.spin[0] += 360.0 * ( x - CAM.lastxy[0] )/800.0;
		CAM.spin[1] += 360.0 * ( y - CAM.lastxy[1] )/800.0;
	}
	
	if (specialKey == GLUT_ACTIVE_ALT) {
	
		CAM.trans[0] += 0.005*( CAM.lastxy[0] - x ); 
		CAM.trans[1] += 0.005*( CAM.lastxy[1] - y );
	}
	
	CAM.lastxy[0] = x;
	CAM.lastxy[1] = y;
	
}

void zoomFunction(int button, int state, int x, int y)
{
	//Zoom in
    if ( button == 3     )
	{
		CAM.trans[2] += 0.08;
	}
	
	//Zoom out
	else if( button == 4 )
	{
		CAM.trans[2] -= 0.08;
	}
}

void renderScene(void)
{
  keyOperations();
  
  long i;
  GLfloat test_coords[8][3] = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, 
                                {0,0,1}, {1,0,1}, {1,1,1}, {0,1,1} };
  GLfloat coords[3];
  
  // Clear the window
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  // Everything is white
  glColor3f(1.0f,  1.0f, 1.0f);
  
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glPushMatrix();
  glTranslatef( -CAM.trans[0], CAM.trans[1], CAM.trans[2] );
  
  glRotatef   (  CAM.spin[0], 0,1,0 );
  glRotatef   (  CAM.spin[1], 1,0,0 );
  
  glPointSize(5.8f);
  glBegin(GL_POINTS);
    for(i = 0; i < SMALL_STARS; i++)
    {
      for( int k=0; k<3; k++ )
      {
        coords[k] = vSmallStars[k + 3*i];
      } 
      glVertex3fv( coords );
    }
  glEnd();
  
  glPopMatrix();
  
  glutSwapBuffers();
  glutPostRedisplay();
}

void idle( void )
{
	glutPostRedisplay();
}

void initGL()
{
    loadSnapshot( 0, data );
    int i;
    
    const GLint texWidth = 256;
    const GLint texHeight = 256;
    const float texHalfWidth = 128.0f;
    const float texHalfHeight = 128.0f;
    printf("INIT: \n");

    unsigned char* pData = new unsigned char[texWidth*texHeight*4];
    for(int y=0; y<texHeight; ++y){
        for(int x=0; x<texWidth; ++x){
            int offs = (x + y*texWidth) * 4;
            float xoffs = ((float)x - texHalfWidth) / texHalfWidth;
            float yoffs = ((float)y - texHalfWidth) / texHalfHeight;
            float alpha = 1.0f - sqrt(xoffs*xoffs + yoffs*yoffs);
            if(alpha < 0.0f)
                alpha = 0.0f;
            pData[offs + 0] = 255; //r
            pData[offs + 1] = 0; //g
            pData[offs + 2] = 0; //b
            pData[offs + 3] = 255.0f * alpha; // * 
        }
    }

    glGenTextures(1, &texture_name);
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, texture_name);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData);
    glEnable(GL_POINT_SPRITE);
    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glPointSize(32.0f);

    glMatrixMode(GL_PROJECTION);
    glOrtho(0, Width, 0, Height, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);

    glEnable(GL_POINT_SMOOTH);

    GLenum e;
    do{
        e = glGetError();
        printf("%s\n",gluErrorString(e));
    } while(e != GL_NO_ERROR);
	  
	  CAM.lastxy[0] = CAM.lastxy[1] = 0.0;
	  CAM.spin[0]   =   CAM.spin[1] = CAM.spin[2] = 0.0;
    CAM.trans[0]  =  CAM.trans[1] = CAM.trans[2] = 0.0;
	  CAM.scale = 1.0;
}

void reshape(int w, int h)
{
	GLfloat fAspect;

	// Prevent a divide by zero
	if(h == 0)
		h = 1;

	// Set Viewport to window dimensions
    glViewport(0, 0, w, h);

	// Calculate aspect ratio of the window
	fAspect = (GLfloat)w/(GLfloat)h;

	// Set the perspective coordinate system
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Field of view of 45 degrees, near and far planes 1.0 and 425
	gluPerspective(45.0f, fAspect, 0.1, 425.0);

	// Modelview matrix reset
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

int main(int argc, char* argv[])
{
	
	
  if( argc != 3 ) 
  {
    fprintf( stderr, "Usage: ./rendersim <config file> <output file>\n" );
    return -1;
  }
  
  SIM_DATA sim;  
  
  FILE *in_config;
  in_config = fopen( argv[1], "r" );
  
  data = fopen( argv[2], "r" );

  // Load the configuration file of the simulation
  parse_config_file( in_config, &sim );

  if( data == NULL ) {
    fprintf( stderr, "CANNOT OPEN CONFIG FILE. ABORTING\n" );
    exit(-10);  
  }
    
  SMALL_STARS = sim.N;
  vSmallStars = (float *)malloc( sizeof(float) * SMALL_STARS * 3 );
  
  glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	
  glutInitWindowSize(SCREEN_X, SCREEN_Y);
	glutCreateWindow("SIMULATION RENDERING");
	
	glutReshapeFunc(reshape);
	glutDisplayFunc(renderScene);
	
  initGL();
  
	// Tell GLUT to use the method "keyPressed" for key presses  
	glutKeyboardFunc(keyPressed);
	// Tell GLUT to use the method "keyUp" for key up events 
	glutKeyboardUpFunc(keyUp);    
	
	glutMouseFunc        (zoomFunction);
	glutPassiveMotionFunc(cameraMovementFunction);
	glutMainLoop();

	return 0;
}
